API Design Patterns — 12장 싱글톤 서브리소스 딥다이브 학습 노트

출처: API Design Patterns (JJ Geewax) | 참고: https://google.aip.dev/


12장에서 다루는 내용

  • 싱글톤 서브리소스의 의미 — 속성도 아니고, 완전한 리소스도 아닌 중간 형태
  • 데이터를 싱글톤 서브리소스로 쪼개야 할 때와 그 이유 — 크기, 보안, 휘발성
  • 표준 메서드와 서브리소스가 상호작용하는 방법 — get/update만 독립 리소스, create/delete는 속성
  • 리소스 계층 구조에서 싱글톤 서브리소스의 위치 — 부모 필수, 최상위 금지

전체 흐름도

[문제 인식]                         [해결 도구]                      [구현 세부사항]
    |                                  |                                |
    v                                  v                                v
+---------------------+     +---------------------+     +---------------------------+
| 리소스 속성이 너무   |     |                     |     | 12.3.1 표준 메서드        |
| 크거나 보안/휘발성   |---->|  싱글톤 서브리소스   |---->| Get: 독립적 리소스        |
| 문제가 있음         |     |  (Singleton Sub-    |     | Update: 독립적 리소스      |
+---------------------+     |   resource)         |     | Create: 속성 (자동 생성)  |
                             |                     |     | Delete: 속성 (부모와 함께)|
+---------------------+     |  = 속성과 리소스의  |     | List: 해당 없음          |
| 예: 운전자 위치가   |---->|    중간 형태        |     +---------------------------+
| 초당 업데이트됨     |     |                     |              |
+---------------------+     +---------------------+     +---------------------------+
                               |           |             | 12.3.2 재설정 (reset)     |
                               v           v             | POST .../location:reset   |
                        +-----------+ +-----------+      +---------------------------+
                        | 부모      | | 서브      |              |
                        | Driver    | | Driver    |      +---------------------------+
                        | {id,name, | | Location  |      | 12.3.3 계층 구조         |
                        |  plate}   | | {lat,long}|      | - 부모 리소스 필수        |
                        +-----------+ +-----------+      | - 최상위 계층 금지        |
                                                         | - 싱글톤의 부모 역할 금지 |
                                                         +---------------------------+

선수 지식 체크리스트

  • [ ] REST API 기본 개념 (리소스, 엔드포인트, HTTP 메서드)
  • [ ] 표준 메서드 (Get, Create, Update, Delete, List)
  • [ ] 필드 마스크 (8장)
  • [ ] 사용자 메서드 (9장) — reset 메서드 이해에 필요

핵심 키워드

키워드 의미
싱글톤 서브리소스 부모 리소스에 딸린 하나뿐인 자식 리소스. 속성처럼 자동 생성/삭제되지만 독립적으로 조회/수정 가능
부모 리소스 싱글톤 서브리소스를 소유하는 상위 리소스 (예: Driver)
캐스케이드 삭제 부모 리소스 삭제 시 하위 서브리소스도 함께 삭제되는 동작
reset 메서드 싱글톤 서브리소스를 기본값으로 되돌리는 사용자 메서드
휘발성 데이터가 자주 변경되는 특성 (예: 위치 정보)

12.1 서론 — 왜 구성 요소를 분리해야 하는가?

한 줄 요약

리소스의 일부 데이터가 너무 크거나, 보안이 다르거나, 자주 바뀌면 별도 서브리소스로 분리하자!

쉬운 설명 (학생증 비유)

학교의 학생 정보 시스템을 생각해보자.

학생 리소스에는 이름, 학번, 반, 사진, 성적표, 건강 기록 등이 있다.

"학생 정보 전체를 하나의 카드에 다 넣으면 어떻게 될까?"

  1. 크기 문제: 사진 파일이 10MB인데, 이름만 확인하려고 10MB를 다운로드해야 한다
  2. 보안 문제: 건강 기록은 보건 선생님만 볼 수 있어야 하는데, 학생 정보를 가져오면 건강 기록까지 같이 온다
  3. 자주 바뀌는 문제: 학생의 현재 위치(교실/운동장/급식실)는 1분마다 바뀌는데, 이름이나 학번은 거의 안 바뀐다

이렇게 성격이 다른 데이터를 하나의 리소스에 다 넣으면 불편하다. 해결책은? 분리하자!

실무 예제 — 승차 공유 API

# 기존: 모든 정보가 Driver 리소스 하나에 있음
interface Driver {
    id: string;
    name: string;
    licensePlate: string;
    locationLatitude: number;    # ← 초당 업데이트 (휘발성!)
    locationLongitude: number;   # ← 초당 업데이트 (휘발성!)
}

문제점: - 운전자 이름만 확인하려는데 위치 데이터까지 딸려옴 - 위치를 업데이트하려면 Driver 전체를 수정해야 함 - 위치 데이터의 업데이트 빈도 ≫ 나머지 데이터

# 개선: 위치 정보를 싱글톤 서브리소스로 분리
interface Driver {
    id: string;
    name: string;
    licensePlate: string;
    # 위치 정보는 여기 없음! → 별도 서브리소스로 분리됨
}

interface DriverLocation {
    id: string;          # drivers/1/location
    lat: number;
    long: number;
    updateTime: Date;
}

분리해야 하는 3가지 이유

1. 크기와 복잡성

예: 아마존 S3 같은 바이너리 저장소 API

# 메타데이터만 필요한 경우가 대부분
GetObject() → 메타데이터 + 바이너리(수 GB) ← 낭비!

# 분리하면
GetObjectMetadata() → 메타데이터만 (수 KB)
GetObjectData()     → 바이너리만 (필요할 때만)

2. 보안

예: 회사 직원 API

# 임직원의 급여 정보는 다른 접근 제약이 필요
GetEmployee() → 이름, 부서, 전화번호, 급여(!!) ← 보안 위험

# 급여를 싱글톤 서브리소스로 분리
GetEmployee()       → 이름, 부서, 전화번호 (일반 접근)
GetEmployeeSalary() → 급여 정보 (HR 팀만 접근 가능)

3. 휘발성

예: 승차 공유 API

# 운전자 위치는 초 단위로 변경
# 이름, 차량번호는 거의 변경되지 않음
# 같은 리소스에 있으면 쓰기 경합(contention) 발생!

# 분리하면
UpdateDriver()         → 이름, 차량번호 (가끔)
UpdateDriverLocation() → 위치 정보 (매초)
→ 서로 독립적으로 업데이트 가능!

핵심 체크포인트

  • ✅ 리소스의 구성 요소 중 크기가 너무 큰 것은 분리한다
  • ✅ 보안 수준이 다른 데이터는 분리한다
  • ✅ 업데이트 빈도가 크게 다른 데이터는 분리한다
  • ✅ 분리의 핵심: 같은 리소스 안의 데이터인데 성격이 다를 때

12.2 개괄 — 싱글톤 서브리소스란?

한 줄 요약

싱글톤 서브리소스는 단순한 속성과 완전한 리소스의 중간 형태이다.

쉬운 설명 (스마트폰 비유)

스마트폰을 생각해보자.

  • 속성: 스마트폰 색상 → 폰 자체의 일부, 따로 관리할 필요 없음
  • 완전한 리소스: 스마트폰에 깔린 앱들 → 여러 개 있고, 각각 독립적으로 설치/삭제
  • 싱글톤 서브리소스: 스마트폰의 배터리 → 폰에 딱 하나 있고, 폰과 함께 생기지만 독립적으로 상태 확인 가능
스마트폰 (부모 리소스)
├── 색상: "블랙"          ← 단순 속성 (폰의 일부)
├── 배터리                ← 싱글톤 서브리소스 (하나만, 독립적 조회/관리)
│   ├── 충전량: 85%
│   └── 건강도: 92%
└── 앱[]                  ← 일반 서브리소스 컬렉션 (여러 개)
    ├── 카카오톡
    ├── 유튜브
    └── ...

싱글톤 서브리소스의 특성

특성 단순 속성 싱글톤 서브리소스 일반 리소스
개수 n/a 부모당 1개 여러 개 가능
생성 부모와 함께 부모와 함께 자동 명시적 Create
삭제 부모와 함께 부모와 함께 자동 명시적 Delete
조회 부모 조회 시 포함 독립적 Get 독립적 Get
수정 부모 수정 시 포함 독립적 Update 독립적 Update
고유 식별자 없음 있음 (부모 ID 기반) 있음

핵심 체크포인트

  • ✅ 싱글톤 서브리소스 = 속성 + 리소스의 하이브리드
  • ✅ 부모 리소스당 딱 1개만 존재
  • ✅ 자동 생성/삭제 (속성처럼) + 독립적 조회/수정 (리소스처럼)

12.3 구현 — 싱글톤 서브리소스의 구체적 동작

12.3.1 표준 메서드

한 줄 요약

Get과 Update는 독립적 리소스처럼, Create와 Delete는 속성처럼, List는 해당 없음.

표준 메서드 동작 요약

표준 메서드 동작 형태 설명
Get 독립적 리소스 고유 식별자로 직접 조회
Update 독립적 리소스 PATCH로 부분 수정 가능
Create 속성 별도 생성 불가, 부모 생성 시 자동 생성
Delete 속성 별도 삭제 불가, 부모 삭제 시 자동 삭제
List n/a 부모당 1개이므로 목록 조회 불필요

Get과 Update — 독립적 리소스처럼

# GET — 서브리소스 직접 조회
GET /drivers/1/location
→ DriverLocation { lat: 40.741718, long: -74.004159 }

# UPDATE — 서브리소스 직접 수정 (PATCH + fieldMask)
PATCH /drivers/1/location
Body: { driverLocation: { lat: 40.742 }, fieldMask: ['lat'] }
→ DriverLocation { lat: 40.742, long: -74.004159 }

핵심: 부모 식별자가 아닌 고유 식별자(drivers/1/location)를 사용한다.

Create — 속성처럼 자동 생성

# Driver를 생성하면 DriverLocation이 자동으로 따라 생성됨
POST /drivers { name: "홍길동", licensePlate: "12가3456" }
→ Driver { id: "drivers/1", name: "홍길동", ... }

# 바로 위치 조회 가능 (이미 존재함)
GET /drivers/1/location
→ DriverLocation { lat: null, long: null }   # 기본값으로 초기화됨

핵심: CreateDriverLocation 같은 메서드는 존재하지 않는다. 부모 리소스(Driver)를 만들면 자동으로 생긴다.

Delete — 캐스케이드 삭제

# 위치 조회
GET /drivers/12345/location
→ DriverLocation { lat: ..., long: ... }

# 부모 삭제
DELETE /drivers/12345
→ 200 OK

# 위치 조회 시도 → 부모가 없으므로 서브리소스도 없음
GET /drivers/12345/location
→ 404 Not Found

핵심: 부모를 삭제하면 싱글톤 서브리소스도 함께 삭제된다 (캐스케이드 삭제).

12.3.2 재설정 (reset)

한 줄 요약

delete 대신 reset 사용자 메서드로 기본값을 복원한다.

왜 delete가 아니라 reset인가?

싱글톤 서브리소스는 삭제할 수 없다 (부모가 살아있는 한 존재). 그래서 "초기화"가 필요하면 reset 사용자 메서드를 사용한다.

# 현재 위치 확인
GET /drivers/1/location
→ DriverLocation { lat: 40.741718, long: -74.004159 }

# 위치 초기화 (리셋)
POST /drivers/1/location:reset
→ DriverLocation { lat: null, long: null }

비유: TV 리모컨의 "초기화" 버튼

TV를 버리는 게 아니라(delete), 설정만 공장 초기화(reset)하는 것과 같다.

12.3.3 계층 구조

한 줄 요약

싱글톤 서브리소스는 반드시 부모 리소스 아래에 있어야 하며, 다른 싱글톤의 부모가 될 수 없다.

규칙 1: 부모 리소스 필수

✅ /drivers/1/location        ← 부모(Driver) 아래에 위치
❌ /location                  ← 최상위에 위치 (금지!)

왜? 최상위 싱글톤은 모든 API 클라이언트가 공유하게 되어 전역 공유 잠금 문제가 생긴다. 모든 쓰기 트래픽이 하나의 리소스에 집중되는 병목.

규칙 2: 싱글톤의 부모 역할 금지

✅ /drivers/1/location                        ← Driver → Location (OK)
❌ /drivers/1/location/signal                 ← Location → Signal (금지!)

왜? 싱글톤이 다른 싱글톤의 부모가 되면 이 리소스가 부모 리소스와 같은 취급을 받아야 하므로 복잡해진다.

12.3.4 최종 API 정의

abstract class RideSharingApi {
    static version = "v1";
    static title = "Ride Sharing API";

    // === Driver 표준 메서드 ===
    @get("/drivers")
    ListDrivers(req: ListDriversRequest): ListDriversResponse;

    @post("/drivers")
    CreateDriver(req: CreateDriverRequest): Driver;

    @get("/{id=drivers/*}")
    GetDriver(req: GetDriverRequest): Driver;

    @patch("/{resource.id=drivers/*}")
    UpdateDriver(req: UpdateDriverRequest): Driver;

    @delete("/{id=drivers/*}")
    DeleteDriver(req: DeleteDriverRequest): void;

    // === DriverLocation 싱글톤 서브리소스 메서드 ===
    @get("/{id=drivers/*/location}")         // Get만 지원
    GetDriverLocation(req: GetDriverLocationRequest): DriverLocation;

    @patch("/{resource.id=drivers/*/location}")  // Update만 지원
    UpdateDriverLocation(req: UpdateDriverLocationRequest): DriverLocation;
}

// 부모 리소스 — 위치가 속성에 없음을 유의
interface Driver {
    id: string;
    name: string;
    licensePlate: string;
}

// 싱글톤 서브리소스 — 부모 ID와 밀접히 연관
interface DriverLocation {
    id: string;          // drivers/1 → drivers/1/location
    lat: number;
    long: number;
    updateTime: Date;
}

핵심 체크포인트

  • ✅ Get/Update만 독립적 리소스처럼 동작
  • ✅ Create/Delete는 부모와 함께 자동 처리
  • ✅ 삭제 대신 reset 사용자 메서드
  • ✅ 반드시 부모 리소스 아래, 최상위 금지
  • ✅ 다른 싱글톤의 부모 역할 금지

12.4 트레이드오프

12.4.1 원자성

한 줄 요약

부모 리소스와 싱글톤 서브리소스를 동시에 원자적으로 수정할 수 없다.

쉬운 설명

# 이것은 불가능:
"Driver의 이름을 바꾸면서 동시에 DriverLocation을 업데이트해줘"

# 두 번의 별도 요청이 필요:
PATCH /drivers/1           { name: "새이름" }
PATCH /drivers/1/location  { lat: 37.5665 }

이건 의도된 것! 분리의 이유가 바로 독립적 관리인데, 동시 수정이 필요하다면 애초에 분리하면 안 된다.

12.4.2 단일 서브리소스 조건

한 줄 요약

같은 타입의 서브리소스는 부모당 하나만 허용된다.

✅ /drivers/1/location    ← DriverLocation 1개 (OK)
❌ /drivers/1/locations   ← DriverLocation 여러 개 (이건 일반 서브컬렉션!)

여러 개가 필요하면 일반 서브컬렉션을 사용해야 한다.


12.5 연습문제 풀이

Q1: 속성 데이터가 어느 정도 크기일 때 싱글톤 서브리소스로 분리하는 것이 적당할까?

A: 명확한 기준은 없지만, 부모 리소스 크기의 대부분을 차지하거나, 대부분의 API 호출에서 필요하지 않은 데이터라면 분리를 고려한다. 예: ACL이 수천 개의 규칙을 가지면 분리 대상.

Q2: 싱글톤 서브리소스가 get과 update만 지원하는 이유는?

A: 부모 리소스가 있는 한 항상 존재해야 하므로 독립적 create/delete가 불가능하다. 부모 생성 시 자동 생성, 부모 삭제 시 자동 삭제된다.

Q3: 왜 delete 대신 reset 사용자 메서드를 지원하는가?

A: 싱글톤 서브리소스는 삭제 자체가 불가능하다 (부모가 살아있는 한). "초기화"가 필요하면 reset으로 기본값을 복원하는 것이 의미적으로 정확하다.

Q4: 싱글톤 서브리소스가 다른 싱글톤의 자식이 될 수 없는 이유는?

A: 싱글톤이 다른 리소스의 부모 역할을 하면 부모 리소스와 같은 취급을 받아야 한다. 이는 싱글톤의 단순함을 해치며, 부모 리소스의 완전 조건을 충족시켜야 하므로 복잡해진다.


부록 A: 용어 사전

용어 영문 의미
싱글톤 서브리소스 Singleton Sub-resource 부모 리소스당 하나만 존재하는 특수한 서브리소스
캐스케이드 삭제 Cascade Delete 부모 삭제 시 하위 리소스도 함께 삭제
휘발성 Volatility 데이터가 자주 변경되는 특성
쓰기 경합 Write Contention 여러 요청이 동일 리소스를 동시에 수정하려 할 때 발생하는 충돌
원자성 Atomicity 여러 작업이 하나의 단위로 전부 성공하거나 전부 실패하는 특성
ACL Access Control List 접근 제어 목록 — 누가 어떤 리소스에 접근 가능한지 규정
reset 메서드 Reset Method 리소스를 기본값으로 되돌리는 사용자 메서드

부록 B: 핵심 비교표

싱글톤 서브리소스 vs 일반 속성 vs 일반 서브리소스

비교 항목 일반 속성 싱글톤 서브리소스 일반 서브리소스
개수 해당 없음 부모당 1개 부모당 N개
생성 부모와 함께 부모와 함께 독립적 Create
삭제 부모와 함께 부모와 함께 독립적 Delete
조회 부모 Get에 포함 독립적 Get 독립적 Get
수정 부모 Update에 포함 독립적 Update 독립적 Update
보안 분리 불가 가능 가능
독립적 URL 없음 있음 있음

표준 메서드 지원 비교

메서드 일반 리소스 싱글톤 서브리소스
Get
Create ❌ (자동)
Update
Delete ❌ (캐스케이드)
List ❌ (1개뿐)
Reset ✅ (사용자 메서드)

부록 C: 추천 참고 자료 & 링크

자료 설명
AIP-156 (google.aip.dev) Google의 싱글톤 리소스 설계 지침
7장 표준 메서드 Get, Create, Update, Delete 동작 원리
8장 필드 마스크 Update 시 부분 수정에 필요한 fieldMask
9장 사용자 메서드 reset 메서드의 기반이 되는 사용자 메서드 개념
난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플